编译器插件

    可以加载编译器插件,它是由用户提供的库用来扩充编译器的行为,例如新的语法扩展,lint检查等。

    一个插件是带有设计好的用来在rustc中注册扩展的注册registrar)函数的一个动态库包装箱。其它包装箱可以使用#![plugin(...)]属性来装载这个扩展。查看rustc::plugin文档来获取更多关于定义和装载插件的机制。

    如果属性存在的话,#![plugin(foo(... args ...))]传递的参数并不由rustc自身解释。它们被传递给插件的Registry

    在绝大多数情况中,一个插件应该通过#![plugin]而不通过extern crate来使用。链接一个插件会将libsyntaxlibrustc加入到你的包装箱的依赖中。基本上你不会希望如此除非你在构建另一个插件。plugin_as_librarylint会检查这些原则。

    通常的做法是将插件放到它们自己的包装箱中,与任何那些会被库的调用者使用的macro_rules!宏或 Rust 代码分开。

    插件可以有多种方法来扩展 Rust 的语法。一种语法扩展是宏过程。它们与普通宏的调用方法一样,不过扩展是通过执行任意Rust代码在编译时操作进行的。

    让我们写一个实现了罗马数字的插件roman_numerals.rs

    1. #![feature(plugin)]
    2. #![plugin(roman_numerals)]
    3. fn main() {
    4. assert_eq!(rn!(MMXV), 2015);
    5. }

    与一个简单的fn(&str) -> u32函数相比的优势有:

    • (任意复杂程度的)转换都发生在编译时
    • 输入验证也在编译时进行
    • 可以扩展并允许在模式中使用,它可以有效的为任何数据类型定义新语法。

    除了宏过程,你可以定义新的类属性和其它类型的扩展。查看Registry::register_syntax_extension和。对于更复杂的宏例子,查看regex_macros

    这里提供一些。

    你可以使用syntax::parse来将记号树转换为像表达式这样的更高级的语法元素:

    看完会给你一个解析基础设施如何工作的感觉。

    保留你解析所有的Span,以便更好的报告错误。你可以用包围你的自定数据结构。

    调用ExtCtxt::span_fatal将会立即终止编译。相反最好调用并返回DummyResult,这样编译器可以继续并找到更多错误。

    上面的例子使用产生了一个普通整数。作为一个AstBuilder特性的额外选择,libsyntax提供了一个准引用宏的集合。它们并没有文档并且非常边缘化。然而,这些将会是实现一个作为一个普通插件库的改进准引用的好的出发点。

    插件可以扩展来添加额外的代码风格,安全检查等。你可以查看src/test/auxiliary/lint_plugin_test.rs来了解一个完整的例子,我们在这里重现它的核心部分:

    1. #![feature(plugin_registrar)]
    2. #![feature(box_syntax, rustc_private)]
    3. extern crate syntax;
    4. #[macro_use]
    5. extern crate rustc;
    6. extern crate rustc_plugin;
    7. use rustc::lint::{EarlyContext, LintContext, LintPass, EarlyLintPass,
    8. EarlyLintPassObject, LintArray};
    9. use rustc_plugin::Registry;
    10. use syntax::ast;
    11. declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'");
    12. struct Pass;
    13. impl LintPass for Pass {
    14. fn get_lints(&self) -> LintArray {
    15. lint_array!(TEST_LINT)
    16. }
    17. }
    18. impl EarlyLintPass for Pass {
    19. if it.ident.name.as_str() == "lintme" {
    20. cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
    21. }
    22. }
    23. }
    24. #[plugin_registrar]
    25. pub fn plugin_registrar(reg: &mut Registry) {
    26. reg.register_early_lint_pass(box Pass as EarlyLintPassObject);
    27. }

    那么像这样的代码:

    将产生一个编译警告:

    1. foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default
    2. foo.rs:4 fn lintme() { }
    3. ^~~~~~~~~~~~~~~

    Lint插件的组件有:

    • 一个或多个declare_lint!调用,它定义了结构
    • 一个用来存放lint检查所需的所有状态(在我们的例子中,没有)
    • 一个定义了如何检查每个语法元素的LintPass实现。一个单独的LintPass可能会对多个不同的Lint调用span_lint,不过它们都需要用get_lints方法进行注册。

    Lint过程是语法遍历,不过它们运行在编译的晚期,这时类型信息时可用的。rustc的与lint插件使用相同的基础构架,并提供了如何访问类型信息的例子。

    由插件定义的语法通常通过属性和插件标识控制,例如,[#[allow(test_lint)]]-A test-lint。这些标识符来自于declare_lint!的第一个参数,经过合适的大小写和标点转换。